#include "webserver.h"

#include<iostream>

WebServer::WebServer(int port, bool trigMode, int timeoutMS, bool optLinger, int threadNum) : port_(port), timeoutMS_(timeoutMS), openLinger_(optLinger), threadPool_(new ThreadPool(threadNum)),
                                                                                              isClose_(false), epoller_(new Epoller()), heapTimer_(new HeapTimer())
{
    srcDir_ = getcwd(nullptr, 256);
    assert(srcDir_);
    strncat(srcDir_, "/resources/", 16);

    HttpConn::userCount = 0;
    HttpConn::srcDir = srcDir_;

    InitEventMode(trigMode);
    if (!InitSocket())
    {
        isClose_ = true;
    }
}

WebServer::~WebServer()
{
    close(listenFd_);
    isClose_ = true;
    free(srcDir_);
}

void WebServer::Start()
{
    int timeMS = -1;
    if (!isClose_)
    {
        LOG(INFO) << "==================== Server Start! ====================";
    }
    while (!isClose_)
    {
        if (timeoutMS_ > 0)
        {
            timeMS = heapTimer_->GetNextTick();
        }
        int eventCnt = epoller_->Wait(timeMS);
        for (int i = 0; i < eventCnt; i++)
        {
            int fd = epoller_->GetEventFd(i);
            uint32_t events = epoller_->GetEvents(i);

            if (fd == listenFd_)
            {
                DealListen();
            }
            else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
            {
                assert(users_.count(fd) > 0);
                CloseConn(&users_[fd]);
            }
            else if (events & EPOLLIN)
            {
                assert(users_.count(fd) > 0);
                DealRead(&users_[fd]);
            }
            else if (events & EPOLLOUT)
            {
                assert(users_.count(fd) > 0);
                DealWrite(&users_[fd]);
            }
            else
            {
                DLOG(ERROR) << "Unknow event!";
            }
        }
    }
}

int WebServer::SetNonblock(int fd)
{
    assert(fd > 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
}

void WebServer::InitEventMode(int trigMode)
{
    listenEvent_ = EPOLLRDHUP;
    connEvent_ = EPOLLONESHOT | EPOLLRDHUP;

    switch (trigMode)
    {
    case 1:
    {
        listenEvent_ |= EPOLLET;
        break;
    }
    case 2:
    {
        connEvent_ |= EPOLLET;
        break;
    }
    case 3:
    {
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }

    default:
    {
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }
    }
}

bool WebServer::InitSocket()
{
    int ret;
    if (port_ > 65535 || port_ < 1024)
    {
        DLOG(ERROR) << "Port Error!";
        return false;
    }

    linger openLinger = {0};
    if (openLinger_)
    {
        openLinger.l_linger = 1;
        openLinger.l_onoff = 1;
    }

    listenFd_ = socket(AF_INET, SOCK_STREAM, 0);
    if (listenFd_ < 0)
    {
        DLOG(ERROR) << "Socket() Error!";
        return false;
    }

    ret = setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &openLinger, sizeof(openLinger));
    if (ret < 0)
    {
        close(listenFd_);
        DLOG(ERROR) << "setsockopt() to openLinger Error!";
        return false;
    }

    int optval = 1;
    ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(optval));
    if (ret < 0)
    {
        close(listenFd_);
        DLOG(ERROR) << "setsockopt() to reuseaddr Error!";
        return false;
    }

    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port_);

    ret = bind(listenFd_, (sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        close(listenFd_);
        DLOG(ERROR) << "bind() Error!";
        return false;
    }

    ret = listen(listenFd_, 6);
    if (ret < 0)
    {
        close(listenFd_);
        DLOG(ERROR) << "listen() Error!";
        return false;
    }

    LOG(INFO) << listenFd_;

    ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN);
    LOG(INFO) << ret;
    if (ret == 0)
    {
        close(listenFd_);
        DLOG(ERROR) << "Epoller AddFd() Error!";
        perror("!!!!");
        return false;
    }
    SetNonblock(listenFd_);
    DLOG(INFO) << "Init Socket Success!";

    return true;
}

void WebServer::DealListen()
{
    sockaddr_in addr;
    socklen_t len = sizeof(addr);
    do
    {
        int fd = accept(listenFd_, (sockaddr *)&addr, &len);
        if (fd < 0)
        {
            return;
        }
        else if (HttpConn::userCount > MAX_FD)
        {
            SendError(fd, "Server Busy");
            LOG(WARNING) << "Client is full!";
            return;
        }
        AddClient(fd, addr);
    } while (listenEvent_ & EPOLLET);
}

void WebServer::CloseConn(HttpConn *conn)
{
    assert(conn);
    conn->Close();
    epoller_->DelFd(conn->GetFd());
    DLOG(INFO) << conn->GetFd() << "disconnected!";
}

void WebServer::DealRead(HttpConn *conn)
{
    assert(conn);
    ExtentTime(conn);
    threadPool_->add_task(std::bind(&WebServer::OnRead, this, conn));
}

void WebServer::DealWrite(HttpConn *conn)
{
    assert(conn);
    ExtentTime(conn);
    threadPool_->add_task(std::bind(&WebServer::OnWrite, this, conn));
}

void WebServer::SendError(int fd, const char *msg)
{
    assert(fd > 0);
    int ret = send(fd, msg, sizeof(msg), 0);
    if (ret < 0)
    {
        LOG(WARNING) << "Send error message error";
    }
    close(fd);
}

void WebServer::AddClient(int fd, sockaddr_in addr)
{
    assert(fd > 0);
    users_[fd].init(fd, addr);
    if (timeoutMS_ > 0)
    {
        heapTimer_->Add(fd, timeoutMS_, std::bind(&WebServer::CloseConn, this, &users_[fd]));
    }
    epoller_->AddFd(fd, connEvent_ | EPOLLIN);
    SetNonblock(fd);
    DLOG(INFO) << fd << " connected!";
}

void WebServer::OnRead(HttpConn *conn)
{
    assert(conn);
    int err;
    ssize_t ret = conn->Read(&err);
    if (ret <= 0 && err != (EAGAIN | EWOULDBLOCK))
    {
        CloseConn(conn);
        return;
    }
    OnProcess(conn);
}

void WebServer::OnWrite(HttpConn *conn)
{
    assert(conn);
    int ret = -1;
    int err = 0;
    ret = conn->Write(&err);
    if(conn->ToWriteByets() == 0) {
        if(conn->IsKeepAlive()) {
            OnProcess(conn);
            return;
        }
    } else if(ret < 0) {
        if(err == EAGAIN || err == EWOULDBLOCK) {
            epoller_->ModFd(conn->GetFd(), connEvent_ | EPOLLOUT);
            return;
        }
    }
    CloseConn(conn);
}

void WebServer::OnProcess(HttpConn *conn)
{
    if(conn->Process()) {
        epoller_->ModFd(conn->GetFd(), connEvent_ | EPOLLOUT);
    } else {
        epoller_->ModFd(conn->GetFd(), connEvent_ | EPOLLIN);
    }
}

void WebServer::ExtentTime(HttpConn *conn)
{
    assert(conn);
    if(timeoutMS_ > 0) {
        heapTimer_->Adjust(conn->GetFd(), timeoutMS_);
    }
}
